package com.study.chunguard.common.utils.date;

import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.text.CharSequenceUtil;

import java.text.SimpleDateFormat;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Objects;

/**
 * 时间工具类
 *
 * @author zhaowenhao
 * @since 2023-08-28
 */
public class DateUtils {
    public static final String BY_DAY = "day";
    public static final String BY_HOUR = "hour";
    public static final String BY_WEEK = "week";
    public static final String BY_SEVEN = "SEVEN";
    public static final String BY_MONTH = "month";
    public static final String BY_SEASON = "season";
    public static final String BY_YESTER_MINUTE = "yesterday_minute";
    public static final String BY_MINUTE = "minute";
    private static final String BY_YEAR = "year";
    private static ThreadLocal<SimpleDateFormat> sdfLocal = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
    private static ThreadLocal<SimpleDateFormat> sdfLocal2 = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM"));
    private static ThreadLocal<SimpleDateFormat> sdfLocal3 = ThreadLocal.withInitial(() -> new SimpleDateFormat("HH:mm"));
    private static ThreadLocal<SimpleDateFormat> sdfLocal4 = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm"));
    private static ThreadLocal<SimpleDateFormat> sdfLocal5 = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
    public static final DateTimeFormatter DEFAULT_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    public static final DateTimeFormatter DAY_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");

    /**
     * 私有构造，禁止 new 对象
     */
    private DateUtils() {
        throw new IllegalStateException("Utility class");
    }

    /**
     * 时区 - 默认
     */
    public static final String TIME_ZONE_DEFAULT = "GMT+8";

    /**
     * 秒转换成毫秒
     */
    public static final long SECOND_MILLIS = 1000;

    public static final String FORMAT_YEAR_MONTH_DAY = "yyyy-MM-dd";

    public static final String FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND = "yyyy-MM-dd HH:mm:ss";

    public static final String FORMAT_HOUR_MINUTE_SECOND = "HH:mm:ss";

    /**
     * 获取指定日期的开始时间（00:00:00.000000000）
     *
     * @param date 输入的日期
     * @return 该日的开始时间，如果输入为null，则返回null
     */
    public static Date getStartOfDay(Date date) {
        if (date == null) {
            return null;
        }
        LocalDate localDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
        LocalDateTime startOfDay = localDate.atStartOfDay();
        return Date.from(startOfDay.atZone(ZoneId.systemDefault()).toInstant());
    }

    /**
     * 获取指定日期的结束时间（23:59:59.999999999）
     *
     * @param date 输入的日期
     * @return 该日的结束时间，如果输入为null，则返回null
     */
    public static Date getEndOfDay(Date date) {
        if (date == null) {
            return null;
        }
        LocalDate localDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
        LocalDateTime endOfDay = localDate.atTime(23, 59, 59, 999999999);
        return Date.from(endOfDay.atZone(ZoneId.systemDefault()).toInstant());
    }

    /**
     * 获取指定日期的结束时间（23:59:59.999999000）,精确到微秒
     *
     * @param date 输入的日期
     * @return 该日的结束时间，如果输入为null，则返回null
     */
    public static Date getEndOfDayToMicrosecond(Date date) {
        if (date == null) {
            return null;
        }
        LocalDate localDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
        LocalDateTime endOfDay = localDate.atTime(23, 59, 59, 999999000);
        return Date.from(endOfDay.atZone(ZoneId.systemDefault()).toInstant());
    }

    /**
     * 获取指定日期的结束时间（23:59:59.999000000）,精确到毫秒
     *
     * @param date 输入的日期
     * @return 该日的结束时间，如果输入为null，则返回null
     */
    public static Date getEndOfDayToMillisecond(Date date) {
        if (date == null) {
            return null;
        }
        LocalDate localDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
        LocalDateTime endOfDay = localDate.atTime(23, 59, 59, 999000000);
        return Date.from(endOfDay.atZone(ZoneId.systemDefault()).toInstant());
    }

    /**
     * 获取指定日期的结束时间（23:59:59）,精确到秒
     *
     * @param date 输入的日期
     * @return 该日的结束时间，如果输入为null，则返回null
     */
    public static Date getEndOfDayToSecond(Date date) {
        if (date == null) {
            return null;
        }
        LocalDate localDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
        LocalDateTime endOfDay = localDate.atTime(23, 59, 59, 0);
        return Date.from(endOfDay.atZone(ZoneId.systemDefault()).toInstant());
    }

    /**
     * 获取当前时间的字符串格式
     *
     * @param date    日期
     * @param pattern 格式
     * @return 当前时间的字符串格式
     */
    public static String formatDate(Date date, String pattern) {
        SimpleDateFormat formatter = new SimpleDateFormat(pattern);
        return formatter.format(date);
    }

    /**
     * 给指定日期增加天数
     *
     * @param date 日期
     * @param days 天数
     * @return 新的日期
     */
    public static Date addDays(Date date, int days) {
        LocalDate localDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
        LocalDate resultDate = localDate.plusDays(days);
        return Date.from(resultDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
    }

    /**
     * 将 LocalDateTime 转换成 Date
     *
     * @param date LocalDateTime
     * @return LocalDateTime
     */
    public static Date of(LocalDateTime date) {
        if (date == null) {
            return null;
        }
        // 将此日期时间与时区相结合以创建 ZonedDateTime
        ZonedDateTime zonedDateTime = date.atZone(ZoneId.systemDefault());
        // 本地时间线 LocalDateTime 到即时时间线 Instant 时间戳
        Instant instant = zonedDateTime.toInstant();
        // UTC时间(世界协调时间,UTC + 00:00)转北京(北京,UTC + 8:00)时间
        return Date.from(instant);
    }

    /**
     * 将时间字符串 转为 Date
     * @param dateTimeStr 时间字符串 （默认格式: yyyy-MM-dd HH:mm:ss）
     * @return Date
     */
    public static Date of(String dateTimeStr) {
        return of(dateTimeStr, DEFAULT_FORMATTER);
    }

    /**
     * 将时间字符串 转为 Date
     * @param dateTimeStr 时间字符串
     * @return Date
     */
    public static Date of(String dateTimeStr, DateTimeFormatter formatter) {
        if (CharSequenceUtil.isBlank(dateTimeStr)) {
            return null;
        }
        // 解析为ZonedDateTime
        ZonedDateTime zonedDateTime = ZonedDateTime.parse(dateTimeStr, formatter);

        // 如果需要转换为java.util.Date
        return Date.from(zonedDateTime.toInstant());
    }

    /**
     * 将 Date 转换成 LocalDateTime
     *
     * @param date Date
     * @return LocalDateTime
     */
    public static LocalDateTime of(Date date) {
        if (date == null) {
            return null;
        }
        // 转为时间戳
        Instant instant = date.toInstant();
        // UTC时间(世界协调时间,UTC + 00:00)转北京(北京,UTC + 8:00)时间
        return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
    }

    public static Date addTime(Duration duration) {
        return new Date(System.currentTimeMillis() + duration.toMillis());
    }

    public static boolean isExpired(Date time) {
        return System.currentTimeMillis() > time.getTime();
    }

    public static boolean isExpired(LocalDateTime time) {
        LocalDateTime now = LocalDateTime.now();
        return now.isAfter(time);
    }

    public static long diff(Date endTime, Date startTime) {
        return endTime.getTime() - startTime.getTime();
    }

    /**
     * 创建指定时间
     *
     * @param year  年
     * @param mouth 月
     * @param day   日
     * @return 指定时间
     */
    public static Date buildTime(int year, int mouth, int day) {
        return buildTime(year, mouth, day, 0, 0, 0);
    }

    /**
     * 创建指定时间
     *
     * @param year   年
     * @param mouth  月
     * @param day    日
     * @param hour   小时
     * @param minute 分钟
     * @param second 秒
     * @return 指定时间
     */
    public static Date buildTime(int year, int mouth, int day,
                                 int hour, int minute, int second) {
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.YEAR, year);
        calendar.set(Calendar.MONTH, mouth - 1);
        calendar.set(Calendar.DAY_OF_MONTH, day);
        calendar.set(Calendar.HOUR_OF_DAY, hour);
        calendar.set(Calendar.MINUTE, minute);
        calendar.set(Calendar.SECOND, second);
        calendar.set(Calendar.MILLISECOND, 0); // 一般情况下，都是 0 毫秒
        return calendar.getTime();
    }

    public static Date max(Date a, Date b) {
        if (a == null) {
            return b;
        }
        if (b == null) {
            return a;
        }
        return a.compareTo(b) > 0 ? a : b;
    }

    public static LocalDateTime max(LocalDateTime a, LocalDateTime b) {
        if (a == null) {
            return b;
        }
        if (b == null) {
            return a;
        }
        return a.isAfter(b) ? a : b;
    }

    /**
     * 计算当期时间相差的日期
     *
     * @param field  日历字段.<br/>eg:Calendar.MONTH,Calendar.DAY_OF_MONTH,<br/>Calendar.HOUR_OF_DAY等.
     * @param amount 相差的数值
     * @return 计算后的日志
     */
    public static Date addDate(int field, int amount) {
        return addDate(null, field, amount);
    }

    /**
     * 计算当期时间相差的日期
     *
     * @param date   设置时间
     * @param field  日历字段 例如说，{@link Calendar#DAY_OF_MONTH} 等
     * @param amount 相差的数值
     * @return 计算后的日志
     */
    public static Date addDate(Date date, int field, int amount) {
        if (amount == 0) {
            return date;
        }
        Calendar c = Calendar.getInstance();
        if (date != null) {
            c.setTime(date);
        }
        c.add(field, amount);
        return c.getTime();
    }

    /**
     * 是否今天
     *
     * @param date 日期
     * @return 是否
     */
    public static boolean isToday(LocalDateTime date) {
        return LocalDateTimeUtil.isSameDay(date, LocalDateTime.now());
    }

    public static Date getInitializeDayTime(int day, int hour, int minute, int second) {
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.DAY_OF_YEAR, day);
        calendar.set(Calendar.HOUR_OF_DAY, hour);
        calendar.set(Calendar.MINUTE, minute);
        calendar.set(Calendar.SECOND, second);
        return calendar.getTime();
    }

    public static void setHorizontal(HashMap<String, Object> trend, String queryType, int amount) {
        Calendar calendar = Calendar.getInstance();
        if (queryType.equals(BY_HOUR)) {
            calendar.set(Calendar.MINUTE, 0);
            calendar.set(Calendar.SECOND, 0);
            /*当前时间前amount小时，插入map*/
            calendar.set(Calendar.HOUR_OF_DAY, calendar.get(Calendar.HOUR_OF_DAY) - amount);
            for (int i = 0; i <= amount; i++) {
                trend.put(sdfLocal3.get().format(calendar.getTime()), "0");
                calendar.add(Calendar.HOUR_OF_DAY, 1);
            }
        }

        if (queryType.equals(BY_SEVEN)) {
            /*近七天日期（不包括今日），插入map*/
            calendar.set(Calendar.DAY_OF_YEAR, calendar.get(Calendar.DAY_OF_YEAR) - (amount + 1));
            for (int i = 0; i <= amount; i++) {
                trend.put(sdfLocal.get().format(calendar.getTime()), "0");
                calendar.add(Calendar.DAY_OF_YEAR, 1);
            }
        }
        if (queryType.equals(BY_WEEK)) {
            /*本周周一到周日的日期，插入map*/
            calendar.set(Calendar.DAY_OF_YEAR, calendar.get(Calendar.DAY_OF_YEAR) - amount);
            for (int i = 0; i < amount; i++) {
                trend.put(sdfLocal.get().format(calendar.getTime()), "0");
                calendar.add(Calendar.DAY_OF_YEAR, 1);
            }
        }

        if (queryType.equals(BY_MONTH)) {
            /*本月1号到月末，插入map*/
            calendar.set(Calendar.DAY_OF_MONTH, 1);
            int month = calendar.get(Calendar.MONTH);
            while (month == calendar.get(Calendar.MONTH)) {
                trend.put(sdfLocal.get().format(calendar.getTime()), "0");
                calendar.add(Calendar.DAY_OF_YEAR, 1);
            }
        }

        if (queryType.equals(BY_YEAR)) {
            /*当前时间前xx个月，格式为yyyy-MM*/
            calendar.add(Calendar.MONTH, -amount);
            calendar.set(Calendar.DATE, 1);
            int i = 0;
            while (i < amount) {
                i++;
                trend.put(sdfLocal2.get().format(calendar.getTime()), "0");
                calendar.add(Calendar.MONTH, 1);
            }
        }

        // 统计今天6：00到24:00时间段（每15分钟统计一次）
        if (queryType.equals(BY_MINUTE)) {
            calendar.set(Calendar.HOUR_OF_DAY, 6);
            calendar.set(Calendar.MINUTE, 0);
            calendar.set(Calendar.SECOND, 0);
            for (int i = 0; i < amount; i++) {
                trend.put(sdfLocal4.get().format(calendar.getTime()), null);
                calendar.add(Calendar.MINUTE, 15);
            }
        }

        // 统计昨天6：00到24:00时间段（每15分钟统计一次）
        if (queryType.equals(BY_YESTER_MINUTE)) {
            calendar.add(Calendar.DAY_OF_YEAR, -1);
            calendar.set(Calendar.HOUR_OF_DAY, 6);
            calendar.set(Calendar.MINUTE, 0);
            calendar.set(Calendar.SECOND, 0);
            for (int i = 0; i < amount; i++) {
                trend.put(sdfLocal4.get().format(calendar.getTime()), null);
                calendar.add(Calendar.MINUTE, 15);
            }
        }
    }

    /**
     * 相差小时数
     *
     * @param startDate 开始事件
     * @param endDate   结束事件
     * @return 小时数
     */
    public static Long betweenHours(Date startDate, Date endDate) {
        if (!checkDate(startDate, endDate)) {
            return 0L;
        }
        return between(startDate, endDate, BY_HOUR);
    }

    /**
     * 相差天数
     *
     * @param startDate 开始事件
     * @param endDate   结束事件
     * @return 天数
     */
    public static Long betweenDays(Date startDate, Date endDate) {
        if (!checkDate(startDate, endDate)) {
            return 0L;
        }
        return between(startDate, endDate, BY_DAY);
    }

    /**
     * 相差分钟
     *
     * @param startDate 开始事件
     * @param endDate   结束事件
     * @return 分钟
     */
    public static Long betweenMinutes(Date startDate, Date endDate) {
        if (!checkDate(startDate, endDate)) {
            return 0L;
        }
        return between(startDate, endDate, BY_MINUTE);
    }

    /**
     * 根据Date日期获取星期
     *
     * @param date 日期
     * @return 星期
     */
    public static String getWeek(Date date) {
        return getWeek(Objects.requireNonNull(of(date)).toLocalDate());
    }

    /**
     * 根据Date日期获取周
     *
     * @param date 日期
     * @return 周
     */
    public static String getShortWeek(Date date) {
        return getShortWeek(Objects.requireNonNull(of(date)).toLocalDate());
    }

    /**
     * 根据LocalDate日期获取星期
     *
     * @param localDate 日期
     * @return 星期
     */
    public static String getWeek(LocalDate localDate) {
        return switchWeek(localDate.getDayOfWeek());
    }

    /**
     * 根据LocalDate日期获取周
     *
     * @param localDate 日期
     * @return 周
     */
    public static String getShortWeek(LocalDate localDate) {
        return switchShortWeek(localDate.getDayOfWeek());
    }

    /**
     * 指定类型两个日期相差数量
     *
     * @param startDate 开始事件
     * @param endDate   结束事件
     * @param type      day、hour、minutes
     * @return 相差数量
     */
    private static Long between(Date startDate, Date endDate, String type) {
        LocalDateTime startDateTime = of(startDate);
        LocalDateTime endDateTime = of(endDate);
        Duration duration = Duration.between(startDateTime, endDateTime);
        switch (type) {
            case BY_HOUR:
                return duration.toHours();
            case BY_MINUTE:
                return duration.toMinutes();
            case BY_DAY:
            default:
                return duration.toDays();
        }
    }

    /**
     * 获取时间字符串(yyyy-MM-dd HH:mm:ss)
     *
     * @param date 时间
     * @return 时间字符串
     */
    public static String format(Date date) {
        return DEFAULT_FORMATTER.format(of(date));
    }

    /**
     * 获取时间字符串(yyyy-MM-dd HH:mm:ss)
     *
     * @param localDateTime 时间
     * @return 时间字符串
     */
    public static String format(LocalDateTime localDateTime) {
        return DEFAULT_FORMATTER.format(localDateTime);
    }

    /**
     * 根据时间格式转为时间字符串
     *
     * @param localDateTime     时间
     * @param dateTimeFormatter 时间格式
     * @return 时间字符串
     */
    public static String format(LocalDateTime localDateTime, DateTimeFormatter dateTimeFormatter) {
        return dateTimeFormatter.format(localDateTime);
    }

    /**
     * 根据时间格式转为时间字符串
     *
     * @param date              时间
     * @param dateTimeFormatter 时间格式
     * @return 时间字符串
     */
    public static String format(Date date, DateTimeFormatter dateTimeFormatter) {
        return dateTimeFormatter.format(of(date));
    }

    /**
     * 当天最小时间 - 2024-01-10 00:00:00
     *
     * @return 格式化后的时间
     */
    public static String todayMinTimeStr() {
        LocalDate today = LocalDate.now();
        return today.atTime(0, 0, 0).format(DEFAULT_FORMATTER);
    }

    /**
     * 当天最小时间 - 2024-01-10 00:00:00
     *
     * @return 时间
     */
    public static Date todayMinTime() {
        LocalDate today = LocalDate.now();
        return of(today.atTime(0, 0, 0));
    }

    /**
     * 当天最大时间 - 2024-01-10 23:59:59
     *
     * @return 格式化后的时间
     */
    public static String todayMaxTimeStr() {
        LocalDate today = LocalDate.now();
        return today.atTime(23, 59, 59).format(DEFAULT_FORMATTER);
    }

    /**
     * 当天最大时间 - 2024-01-10 23:59:59
     *
     * @return 时间
     */
    public static Date todayMaxTime() {
        LocalDate today = LocalDate.now();
        return of(today.atTime(23, 59, 59));
    }

    private static Boolean checkDate(Date... dates) {
        for (Date date : dates) {
            if (date == null) {
                return Boolean.FALSE;
            }
        }
        return Boolean.TRUE;
    }

    private static String switchWeek(DayOfWeek dayOfWeek) {
        switch (dayOfWeek) {
            case MONDAY:
                return "星期一";
            case TUESDAY:
                return "星期二";
            case WEDNESDAY:
                return "星期三";
            case THURSDAY:
                return "星期四";
            case FRIDAY:
                return "星期五";
            case SATURDAY:
                return "星期六";
            case SUNDAY:
                return "星期日";
            default:
                return "未知";
        }
    }

    private static String switchShortWeek(DayOfWeek dayOfWeek) {
        switch (dayOfWeek) {
            case MONDAY:
                return "周一";
            case TUESDAY:
                return "周二";
            case WEDNESDAY:
                return "周三";
            case THURSDAY:
                return "周四";
            case FRIDAY:
                return "周五";
            case SATURDAY:
                return "周六";
            case SUNDAY:
                return "周日";
            default:
                return "未知";
        }
    }

}
